﻿using System;
using System.Collections.Generic;
using Plugins;
using Plugins.Windows;
using System.Xml;
using System.Xml.XPath;
using System.Linq;
using System.Windows.Controls;
using XPathPlugin.XPathTreeUtils;
using XPathPlugin.XPathExpressionUtils;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Threading;


namespace XPathPlugin
{
    public class XPathCore: IPlugin
    {
        #region Variables

        protected enum EventType {FILE_VALIDATION_FAILED, EXPRESSION_VALIDATION_FAILED, 
                                  FILE_VALIDATION_OK, EXPRESSION_VALIDATION_OK, 
                                  SHOWED, PROGRAM_FAILED};

        private ICore _core;
        private TreeListView _view;
        private RichTextBox _expressionBox;        

        private static readonly string _PluginName = "XPath Plugin";        
        private static readonly string _FileFilter = " XML (*.xml)|*.xml";
        private static readonly string _ExpressionLanguageName = "XPath";
        private static readonly string _PluginMenuName = "XPath";
        private static readonly string _EvaluateButtonContent = "Evaluate";
        private static readonly string _NextButtonContent = "Next";
        
        private string _filePath;

        private int _expressionIndex;        

        private bool _isFileValid;
        private bool _isExpressionValid;
        private bool _hasFocus;
        private bool _debugMode;


        private XmlDocument _dom;
        private XmlReader _xmlReader;
        private XmlNamespaceManager _xmlNM;

        private XPathExpression _breakPointExpression;
        private string _expression;

        private XPathExpressionParser _parser;
        private XPathTreeTransformer _transformer;
        private XPathHighlighter _highlighter;
        private XPathGraphics _graphics;               

        private Dictionary<XmlNode, TreeListViewItem> _nodeMapping;
        private Dictionary<XPathExpression, XmlNodeList> _expressionMapping;

        private RoutedEventHandler _showTreeClickedHandler;
        private KeyEventHandler _keyPressHandler;
        private EventHandler<FocusChangedArgs> _focusChangedHandler;
        private EventHandler<ButtonClickedArgs> _buttonClickedHandler;
        private EventHandler<FileChoosedArgs> _fileChoosedHandler;

        private Thread _loadFileThread;
                
        #endregion

        public XPathCore()
        {
            _isFileValid = false;
            _isExpressionValid = false;
            _hasFocus = false;
            _debugMode = false;
            _expressionIndex = -1;

            _nodeMapping = new Dictionary<XmlNode, TreeListViewItem>();
            _expressionMapping  = new Dictionary<XPathExpression, XmlNodeList>();
            _showTreeClickedHandler = new RoutedEventHandler(OnShowTreeClicked);
            _keyPressHandler = new KeyEventHandler(OnKeyPress);
            _focusChangedHandler = new EventHandler<FocusChangedArgs>(OnFocusChanged);
            _buttonClickedHandler = new EventHandler<ButtonClickedArgs>(OnButtonClicked);
            _fileChoosedHandler = new EventHandler<FileChoosedArgs>(OnFileChoosed);
        }

        #region IPlugin Members
       
        /// <summary>
        /// Raise after the file is shown.
        /// </summary>
        public event EventHandler<StateEventArgs> Event;

        /// <summary>
        /// Gets or sets whether the IPlugin has focus. (Is set to active)
        /// </summary>
        public bool HasFocus
        {
            get { return _hasFocus; }
            set 
            { 
                _hasFocus = value;
                if (_hasFocus && _core != null)
                {
                    // Reset
                    Reset();

                    // Register handlers
                    _core.ExpressionBox.KeyUp += _keyPressHandler;                    
                    _core.FocusChanged += _focusChangedHandler;
                    _core.ButtonClicked += _buttonClickedHandler;
                    _core.FileChoosed += _fileChoosedHandler;

                    // Reinit text box
                    _core.Expression = _core.Expression;

                    // Try to load properties from ICore
                    try
                    {
                        string debugValue = _core.Settings["debug"];
                        _debugMode = debugValue != null && Boolean.Parse(debugValue);
                    }
                    catch (Exception) { _debugMode = false; }
                    
                    // For debug mode show plugin menu
                    if (_debugMode)
                        XPathGraphics.ShowPluginMenu(_core.PluginMenu, _PluginMenuName, _showTreeClickedHandler);
                }
                // Unregister handlers
                else if (!_hasFocus && _core != null) {
                    _core.ExpressionBox.KeyUp -= _keyPressHandler;
                    _core.FocusChanged -= _focusChangedHandler;
                    _core.ButtonClicked -= _buttonClickedHandler;
                    _core.FileChoosed -= _fileChoosedHandler;
                }
            }
        }

        /// <summary>
        /// Gets plugin name
        /// </summary>
        public string PluginName
        {
            get { return _PluginName; }
        }       

        /// <summary>
        /// Gets file filter string for OpenFileDialog
        /// </summary>
        public string FileFilter
        {
            get { return _FileFilter; }
        }

        /// <summary>
        /// Gets expression language name.
        /// </summary>
        public string ExpressionLanguageName
        {
            get { return _ExpressionLanguageName; }
        }

        /// <summary>
        /// Gets or sets ICore instance.
        /// </summary>
        public ICore Core
        {
            get { return _core; }
            set { 
                _core = value;
                if (_core != null)
                {                                                        
                    // Set components from ICore
                    _view = _core.View;
                    _expressionBox = _core.ExpressionBox;                                        
                }
            }
        }        

        #endregion                    

        #region XPathCore

        /// <summary>
        /// Load file to XML DOM
        /// </summary>
        /// <param name="filePath">Filepath of file to load</param>
        private void LoadFile()
        {           
            try
            {
                // Load file to Xml DOM
                _xmlReader = new XmlTextReader(_filePath);
                _dom = new XmlDocument();
                _dom.Load(_xmlReader);

                _xmlNM = new XmlNamespaceManager(_dom.NameTable);

                // Set Valid Xml file
                _isFileValid = true;

                // Reset
                Reset();

                // Raise FileValidation event
                RaiseEvent(EventType.FILE_VALIDATION_OK);

                //ShowFile();
            }
            catch (Exception e)
            {
                // Set Invalid Xml file
                _isFileValid = false;
                
                // Raise FileValidation event
                RaiseEvent(EventType.FILE_VALIDATION_FAILED);
            }            
        }

        /// <summary>
        /// Show XML file
        /// </summary>
        private void ShowFile()
        {
            if (_dom != null)
            {
                // Show file
                _graphics = new XPathGraphics(_dom, _xmlNM, _view, _nodeMapping);
                _graphics.Show();

                RaiseEvent(EventType.SHOWED);
            }
        }
        
        /// <summary>
        /// Gets or set expression
        /// </summary>
        public string Expression
        {
            get { return _expression; }
            set
            {
                // If new expression wants to be set
                if (_expression == null || !_expression.Equals(value))
                {
                    try
                    {
                        // Reset
                        Reset();

                        // Set normalized expression
                        _expression = XPathExpressionNormalizer.Normalize(value);

                        // Check if it's XPath expression
                        IsExpression(_expression);

                        // Normalize expression box
                        NormalizeExpressionBox();

                        // Parse expression
                        _parser = new XPathExpressionParser(_expression);

                        // Initialize transformer
                        _transformer = new XPathTreeTransformer(_parser.XPathTree, _expressionBox);

                        // Create expression mapping
                        CreateExpressionMapping();

                        // Initialize ExpressionHighlighter
                        _highlighter = new XPathHighlighter(_nodeMapping, _expressionMapping, _transformer.ExpressionTextMap, _view, _expressionBox);

                        // Set ValidExpression
                        _isExpressionValid = true;

                        // Raise ExpressionValidated event
                        RaiseEvent(EventType.EXPRESSION_VALIDATION_OK);

                    }
                    catch (XPathException)
                    {
                        // Set False Expression
                        _isExpressionValid = false;

                        // Raise ExpressionValidated event
                        RaiseEvent(EventType.EXPRESSION_VALIDATION_FAILED);
                    }
                    catch (ArgumentException)
                    {
                        // Set False Expression
                        _isExpressionValid = false;

                        // Raise ExpressionValidated event
                        RaiseEvent(EventType.EXPRESSION_VALIDATION_FAILED);
                    }
                    catch (PluginException)
                    {
                        // Set False Expression
                        _isExpressionValid = false;

                        // Raise ExpressionValidated event
                        RaiseEvent(EventType.PROGRAM_FAILED);
                    }
                    catch (Exception)
                    {
                        // Set False Expression
                        _isExpressionValid = false;

                        // Raise ExpressionValidated event
                        RaiseEvent(EventType.PROGRAM_FAILED);
                    }                    
                }
            }
        }

        /// <summary>
        /// Raises event
        /// </summary>
        /// <param name="type">type of event to raise</param>
        protected void RaiseEvent(EventType type) 
        {
            StateEventArgs.PluginState state = StateEventArgs.PluginState.SHOWED;
            switch (type)
            {
                case EventType.FILE_VALIDATION_OK:
                    state = StateEventArgs.PluginState.FILE_VALIDATION_OK;
                    break;

                case EventType.EXPRESSION_VALIDATION_OK:
                    state = StateEventArgs.PluginState.EXPRESSION_VALIDATION_OK;
                    break;

                case EventType.FILE_VALIDATION_FAILED:
                    state = StateEventArgs.PluginState.FILE_VALIDATION_FAILED;
                    break;

                case EventType.EXPRESSION_VALIDATION_FAILED:
                    state = StateEventArgs.PluginState.EXPRESSION_VALIDATION_FAILED;
                    break;

                case EventType.PROGRAM_FAILED:
                    state = StateEventArgs.PluginState.PROGRAM_FAILED;
                    break;

                case EventType.SHOWED:                
                    state = StateEventArgs.PluginState.SHOWED;
                    break;

            }

            if (Event != null)         
                Event(this, new StateEventArgs(state));
        }

        /// <summary>
        /// Creates mapping between XPathExpression and NodeCollection (its value)
        /// </summary>
        private void CreateExpressionMapping()
        {
            _expressionMapping.Clear();

            foreach (XPathExpression expression in _transformer.ExpressionList)            
                _expressionMapping.Add(expression, _dom.SelectNodes(expression.Expression, _xmlNM));            
        }        

        /// <summary>
        /// Evaluate expression writen in expression RichTextBox. 
        /// </summary>
        private void Evaluate()
        {
            if (_isFileValid && _isExpressionValid)
            {
                foreach (XPathExpression expression in _expressionMapping.Keys)
                {
                    if (_breakPointExpression != null) {
                        if (_breakPointExpression == expression)
                            _breakPointExpression = null;
                        
                        continue;
                    }

                    if (IsBreakPointContained(expression))
                    {
                        _breakPointExpression = expression;
                        _core.EvaluateButtonContent = _NextButtonContent;
                        _highlighter.Highlight(expression, true);
                        return;
                    }                    
                }

                if (_breakPointExpression == null)
                {
                    _core.EvaluateButtonContent = _EvaluateButtonContent;
                    _expressionIndex = -1;
                    _highlighter.HighlightAll();  
                }                                                
            }
        }

        /// <summary>
        /// Evaluate defined XPathExpressin
        /// </summary>
        /// <param name="expression">expression to evaluate</param>
        private void Evaluate(XPathExpression expression)
        {
            if (_isFileValid && _isExpressionValid)
            {
                _breakPointExpression = expression;
                _highlighter.Highlight(expression, true);
            }
        }

        /// <summary>
        /// Reset XPathCore to default
        /// </summary>
        private void Reset()
        {
            _expression = null;
            _breakPointExpression = null;            
            _core.EvaluateButtonContent = _EvaluateButtonContent;
            _expressionIndex = -1;

            if (_highlighter != null)
                _highlighter.RemoveHighlights();
        }

        /// <summary>
        /// Normalize expression box - remove nestting spaces, parenthesis.
        /// Set font to default
        /// </summary>
        private void NormalizeExpressionBox()
        {
            if (_graphics != null)
                _core.Expression = _expression;
        }

        /// <summary>
        /// Returns if some breakpoint is contained during evaluating expression
        /// </summary>
        /// <param name="expression">expression to check</param>
        /// <returns>bool</returns>
        private bool IsBreakPointContained(XPathExpression expression)
        {
            if (expression == null)
                return false;
            
            foreach (XmlNode node in _expressionMapping[expression])
                try
                {
                    if (_nodeMapping[node].IsChecked)
                        return true;
                }
                catch (Exception) { return false; }

            return false;
        }

        /// <summary>
        /// Checks if expression is XPathExpression
        /// If no, throws exception
        /// </summary>
        /// <param name="expression">expression to check</param>
        private void IsExpression(string expression)
        {
            XPathExpression.Compile(expression);
        }

        /// <summary>
        /// Handle window button clicked
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">ButtonArgs</param>
        private void OnButtonClicked(object sender, ButtonClickedArgs e)
        {
            switch (e.WindowButtonType)
            {
                case ButtonClickedArgs.ButtonType.BackButton:
                    OnBackButtonClicked();
                    break;

                case ButtonClickedArgs.ButtonType.NextButton:
                    OnNextButtonClicked();
                    break;

                case ButtonClickedArgs.ButtonType.EvaluateButton:
                    OnEvaluateButtonClicked();
                    break;
            }
        }

        /// <summary>
        /// Handle evaluate button click
        /// </summary>
        private void OnEvaluateButtonClicked()
        {
            if (_core != null && _graphics != null)
            {
                Expression = _core.Expression;
                if (_expression == null)
                    return;

                Evaluate();
            }
        }

        /// <summary>
        /// Handle back button click
        /// </summary>
        private void OnBackButtonClicked()
        {
            if (_core != null && _graphics != null)
            {
                Expression = _core.Expression;
                if (_expression == null)
                    return;

                if (_isFileValid && _isExpressionValid)
                {
                    _expressionIndex = _expressionIndex - 1 < 0 ? _transformer.ExpressionList.Count - 1 : _expressionIndex - 1;
                    XPathExpression expression = _transformer.ExpressionList.ElementAt<XPathExpression>(_expressionIndex);
                    Evaluate(expression);
                }
            }
        }

        /// <summary>
        /// Handle next button click
        /// </summary>
        private void OnNextButtonClicked()
        {
            if (_core != null && _graphics != null)
            {
                Expression = _core.Expression;
                if (_expression == null)
                    return;

                if (_isFileValid && _isExpressionValid)
                {
                    _expressionIndex = _expressionIndex + 1 > _transformer.ExpressionList.Count - 1 ? 0 : _expressionIndex + 1;
                    XPathExpression expression = _transformer.ExpressionList.ElementAt<XPathExpression>(_expressionIndex);
                    Evaluate(expression);
                }
            }
        }          

        /// <summary>
        /// Handle focus change event
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">FocusChanged args</param>
        private void OnFocusChanged(object sender, FocusChangedArgs e)
        {
        }

        /// <summary>
        /// Handle event file choosed
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">event args</param>
        private void OnFileChoosed(object sender, FileChoosedArgs e)
        {                        
            _filePath = e.FilePath;
            Mouse.OverrideCursor = Cursors.Wait;
            
            LoadFile();
            ShowFile();

            Application.Current.MainWindow.UpdateLayout();
            Mouse.OverrideCursor = null;            
        }

        /// <summary>
        /// Delegate method for PluginMenu.Clicked event.
        /// Shows MessageBox with debug string
        /// </summary>
        /// <param name="sender">pluginMenu</param>
        /// <param name="e">Event args</param>
        private void OnShowTreeClicked(object sender, RoutedEventArgs e)
        {
            try
            {
                string expression = XPathExpressionNormalizer.Normalize(_core.Expression);
                string valueToShow = "";
                IsExpression(expression);

                if (!expression.Equals(_core.Expression))
                    _core.Expression = expression;

                XPathExpressionParser parser = new XPathExpressionParser(expression);
                XPathTreeTransformer transformer = new XPathTreeTransformer(parser.XPathTree, _core.ExpressionBox);

                valueToShow += "######## XPath Tree ########";
                valueToShow += System.Environment.NewLine + System.Environment.NewLine;
                valueToShow += parser.ToString();
                valueToShow += System.Environment.NewLine + System.Environment.NewLine;
                valueToShow += "######## Built expressions ########";
                valueToShow += System.Environment.NewLine + System.Environment.NewLine;
                valueToShow += transformer.ToString();
                MessageBox.Show(valueToShow);
            }
            catch (XPathException)
            {
                MessageBox.Show("Bad expression");
            }
            catch (ArgumentException)
            {
                MessageBox.Show("Bad expression");
            }
            catch (PluginException)
            {
                MessageBox.Show("Application error - plugin failed.");
            }
            catch (Exception)
            {
                MessageBox.Show("Application runtime error.");
            }

        }

        /// <summary>
        /// Delegate method for expression box keyup event.
        /// Normalize text in expression box and TreeViewItems
        /// </summary>
        /// <param name="sender">Expression box</param>
        /// <param name="e">Event args</param>
        private void OnKeyPress(object sender, KeyEventArgs e)
        {            
            if (e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up || e.Key == Key.Down || e.Key == Key.Enter || e.Key == Key.Space)
                return;

            _expressionBox.BeginInit();

            if (_highlighter != null)
            {
                _highlighter.RemoveHighlights();                
            }

            _expressionBox.EndInit();
                  
        }
                                                              
        #endregion
    }
}
